import * as THREE from "three"
import { NullGeneratorResult, WaitForPromiseGeneratorResult } from "../engine/EngineGenerators"
import { TourTransitionType } from "../enum/tourTransition.enum"
import transitionTypeEnum from "../enum/transitionType.enum"
import y from "../math/19674"
import E from "../math/79437"
import Logger from "../utils/Logger"
import OpenDeferred from "../utils/OpenDeferred"
import TourTransitionFactory from "./TourTransitionFactory"
const F = 0.001
const X = 5
const k = (e, t, i, s) => {
  const n = Math.min(e.distanceTo(t), 5),
    r = n * (1 / s) * 1000
  let o = r
  const a = i / r
  if (a > F) {
    o += o * ((a - F) / F)
  }
  const h = Math.abs(e.clone().setX(0).setZ(0).distanceTo(t.clone().setX(0).setZ(0)) / Math.max(n, 1))
  if (h > 0.1) {
    o *= 0.9 + 0.75 * h
  }
  return o
}
const N = new Logger("tours")
export default class TourSmoothWalkingTransition {
  settingsData: any
  cameraPose: any
  cameraTransition: any
  sweepTransition: any
  sweepControl: any
  cameraControl: any
  generators: any
  setRestrictedSweeps: any
  getCurve: any
  type: number
  toIndex: number
  currentTransitionGenerator: any
  currentTransitionPromise: any
  canceling: boolean
  buildTransition: (
    e: any,
    t: any
  ) => { generator: () => Generator<WaitForPromiseGeneratorResult | NullGeneratorResult, void, unknown>; deferred: OpenDeferred<any> }
  constructor(e, t, i, n, r, o, a, h, d) {
    ;(this.settingsData = e),
      (this.cameraPose = t),
      (this.cameraTransition = i),
      (this.sweepTransition = n),
      (this.sweepControl = r),
      (this.cameraControl = o),
      (this.generators = a),
      (this.setRestrictedSweeps = h),
      (this.getCurve = d),
      (this.type = TourTransitionType.Path),
      (this.toIndex = -1),
      (this.currentTransitionGenerator = null),
      (this.currentTransitionPromise = null),
      (this.canceling = !1),
      (this.buildTransition = (e, t) => {
        if (e.length <= 2) throw (N.debug(`invalid path: ${e}`), new Error("smooth path requires more than 2 stops"))
        const i = this,
          n = new OpenDeferred()
        i.setRestrictedSweeps(e)
        const r = e.map(e => e.position),
          o = i.getCurve(r),
          a = E.vG,
          h = E.Fs,
          d = E.to,
          c = TourTransitionFactory.getTransitionSpeed(i.settingsData),
          u = ((e, t, i) => {
            let s = 0
            for (let n = 0; n < e.length - 1; n++) {
              const r = y.zW(t[n], t[n + 1])
              s += k(e[n].position, e[n + 1].position, r, i)
            }
            return s
          })(e, t, c)
        N.debug(`path duration: ${u.toFixed(0)}ms, at speed: ${c}m/s`), i.cameraControl.beginExternalTransition()
        return {
          generator: function* () {
            n.notify(0)
            const r = new THREE.Vector3(),
              p = new THREE.Quaternion()
            let g = 0,
              m = 0,
              w = 0,
              f = 1,
              v = e[f].id
            yield new WaitForPromiseGeneratorResult(i.sweepControl.activateSweepUnsafe({ sweepId: v })),
              i.sweepControl.beginSweepTransition({
                sweepId: v,
                transitionTime: u,
                internalProgress: !1
              }),
              e.length > 2 && i.sweepControl.activateSweepUnsafe({ sweepId: e[2].id })
            let T = i.cameraPose.rotation.clone()
            for (; g < u && !i.canceling; ) {
              const l = g / u
              w > 0 && (T = t[w].clone())
              const c = t[f],
                S = o.normalSourceDistances[w],
                P = o.normalSourceDistances[f],
                D = e[f]
              if (((m = y.et(l, S, P, 0, 1)), m <= 1)) {
                const e = d(m, 0, 1, 1)
                i.sweepTransition.progress.modifyAnimation(e, 1, 0)
                const t = h(m, 0, 1, 1)
                p.copy(T).slerp(c, t)
              }
              l < 1 && r.copy(o.curve.getPointAt(a(l, 0, 1, 1))),
                i.cameraControl.updateCameraPosition(r),
                i.cameraControl.updateCameraRotation(p),
                l >= P &&
                  (i.sweepControl.endSweepTransition({ sweepId: D.id }),
                  w++,
                  f++,
                  (v = e[w + 1].id),
                  yield new WaitForPromiseGeneratorResult(i.sweepControl.activateSweepUnsafe({ sweepId: v })),
                  i.sweepControl.beginSweepTransition({
                    sweepId: v,
                    transitionTime: u,
                    internalProgress: !1
                  }),
                  e.length > w + 2 &&
                    i.sweepControl.activateSweepUnsafe({
                      sweepId: e[w + 2].id
                    })),
                n.notify(l),
                (g = Date.now() - i.cameraTransition.startTime),
                yield new NullGeneratorResult()
            }
            if ((i.cameraControl.endExternalTransition(), i.canceling)) {
              i.canceling = !1
              const t = e[f].position,
                n = k(i.cameraPose.position, t, 0, c),
                r = i.cameraControl.moveTo({
                  transitionTime: n / X,
                  transitionType: transitionTypeEnum.Interpolate,
                  pose: { position: t }
                })
              r.progress(e => {
                const t = y.et(e, 0, 1, m, 1)
                i.sweepTransition.progress.modifyAnimation(t, 1, 0)
              }),
                yield new WaitForPromiseGeneratorResult(r.nativePromise())
            }
            i.sweepControl.endSweepTransition({ sweepId: v }), i.setRestrictedSweeps(null), n.notify(1), n.resolve()
          },
          deferred: n
        }
      })
  }
  get active() {
    return null !== this.currentTransitionPromise || null !== this.currentTransitionGenerator
  }
  get promise() {
    return this.currentTransitionPromise ? this.currentTransitionPromise : Promise.resolve()
  }
  async stop() {
    ;(this.canceling = !0),
      this.currentTransitionPromise && (await this.promise, (this.currentTransitionPromise = null)),
      this.currentTransitionGenerator && (this.generators.stopGenerator(this.currentTransitionGenerator), (this.currentTransitionGenerator = null))
  }
  start(e) {
    if ((N.debug(`starting smooth transition with ${e.path.length - 1} stops`), this.active)) throw Error("Transition already active")
    this.canceling = !1
    const { generator: t, deferred: i } = this.buildTransition(e.path, e.orientations)
    this.generators.startGenerator(t)
    this.currentTransitionGenerator = t
    this.currentTransitionPromise = i.nativePromise().then(() => {
      this.currentTransitionPromise = null
      this.currentTransitionGenerator = null
    })
    return this
  }
}
